﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using Sampler;
using Shapes.Misc;
using Shapes.Geometry;
using Shapes.Misc.Appearance;
using ShapesExtensions;
using TestGame.HelperClasses;

namespace TestGame.Samples
{
    /// <summary>
    /// in this sample we will create some shapes and link one of them to the mouse cursor.
    /// we detect collisions between the mouse shape and the others using an instance of CollisionDetector2D.
    /// by pressing + and - the user is able to change the accuracy of the detection which influences the performance.
    /// to switch the mouse shape just click the right mouse button.
    /// if the user tried the Shape-Composition sample, one of the shapes is the self created composition. 
    /// Otherwise it is a yin and yang
    /// </summary>
    public class CollisionSample1 : Sample
    {
#if XNA4
        static readonly Color NormalColor = Color.Black * 0.5f;
        static readonly Color CollisionColor = Color.White * 0.5f;
#else
        static readonly Color NormalColor = new Color(Color.Wheat, 128);
        static readonly Color CollisionColor = new Color(Color.Red, 128);
#endif
   
        #region Fields

        // a class which allows to draw a shape into a texture
        TextureGenerator _Generator;
        // the CollisionDetector2D can check if shapes or drawings are colliding
        CollisionDetector2D _Detector;

        // a helper which lets control the active shape with any input
        // look into the HelperClasses Folder to see how this class works
        CursorHelper _Cursor;

        // Reference to one of the shapes which is linked to the cursor
        GeometrySprite _MouseShape;

        // List with textures displaying the shapes
        List<GeometrySprite> _Shapes = new List<GeometrySprite>();

        #endregion

        // constructor
        public CollisionSample1(TextureGenerator textureGenerator)
            : base("")
        {
            _Generator = textureGenerator;
            _Cursor = new TestGame.HelperClasses.CursorHelper(Game.GraphicsDevice);
            _Cursor.IsDigitalGamepadMovementEnabled = false; // we use the D-Pad for rotation and scale
            _Cursor.ButtonReleased += _Cursor_ButtonReleased;
        }



        #region Initialize & Deactivate

        protected override void  Initialize()
        {
            // create a CollisionDetector to auto-detect colliding geometry.
            _Detector = new CollisionDetector2D();
            _Detector.CollisionDetected += Detector_OnCollision;
            
            SetText(); // the description can only be created when _Detector has a value

            #region create shapes
            _Shapes.Add(new GeometrySprite(
                GeometryTemplates.CreateRoundedRect(100, 100, 20), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Ellipse(Vector2.Zero, 40, 40), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Ellipse(Vector2.Zero, 50, 30), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Rect(30, 80), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Rect(50, 70), _Generator, GetRandomFill()));
            
            _Shapes.Add(new GeometrySprite(
                new Triangle(new Vector2(30, 40), new Vector2(-10, 7), new Vector2(10, 4)), _Generator, GetRandomFill()));
            
            _Shapes.Add(new GeometrySprite(
                new Polygon(GeometryTemplates.CreateArrow(Vector2.Zero, Vector2.One * 30, 5, 10, 10)), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new SpriteShape("mandelbrot", new AlphaThreshold(128, false)), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Polygon(GeometryTemplates.CreateKochSnowflake(24, 2, 0)), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new Polygon(GeometryTemplates.CreateNGramm(5, 80, 0)), _Generator, GetRandomFill()));

            _Shapes.Add(new GeometrySprite(
                new SplinePoly(new Spline(
                                               new SplinePoint(50,  20,   false),

                                               new SplinePoint(60,   0,    true),
                                               new SplinePoint(90,   0,    true),
                                               new SplinePoint(100, 25,    true),
                                               new SplinePoint(90,  40,    true),
                                               new SplinePoint(55,  60,    true),

                                               new SplinePoint(50,  80,   false),

                                               new SplinePoint(45,  60,    true),
                                               new SplinePoint(10,  40,    true),
                                               new SplinePoint(0,   25,    true),
                                               new SplinePoint(10,   0,    true),
                                               new SplinePoint(40,   0,    true)
                                            )), _Generator, GetRandomFill()));

            // If the user tried the third sample, we will add his creation
            if (ShapeCompositionSample.Instance != null 
                && ShapeCompositionSample.Instance.IsInitialized)
            {
                _Shapes.Add(new GeometrySprite(
                    (Shape)ShapeCompositionSample.Instance.Composition.Clone(), _Generator, GetRandomFill()));
            }
            else // otherwise a yin and yang
            {
                _Shapes.Add(new GeometrySprite(
                    GeometryTemplates.CreateYinYang(80, 10, 3), _Generator, GetRandomFill()));
            }
            #endregion

            #region positioning in a circle

            Vector2 pos = new Vector2();      // position to calculate
            float rot;        // rotation in radians
            float rad = 200;  // radius of the circle

            // start with one, because the shape with index 0 is linked to the mouse at the beginning
            for (int i = 1; i < _Shapes.Count; i++) 
            {
                rot = i * (MathHelper.TwoPi / (_Shapes.Count - 1));
                pos.X = (int)(Game.GraphicsDevice.PresentationParameters.BackBufferWidth / 2 
                    + (float)Math.Cos(rot) * rad);
                pos.Y = (int)(Game.GraphicsDevice.PresentationParameters.BackBufferHeight / 2
                    + (float)Math.Sin(rot) * rad);

                _Shapes[i].Geometry.Origin = new Vector2(_Shapes[i].Geometry.Width / 2, _Shapes[i].Geometry.Height / 2);
                _Shapes[i].Geometry.Position = pos;

                _Shapes[i].Color = NormalColor;

                // add all shapes as passive collision Objects to the CollisionDetector
                _Detector.SetupGeometry(_Shapes[i].Geometry, false, true);
            }
            #endregion

            // Mouse has the first shape
            _MouseShape = _Shapes[0];
            _MouseShape.Geometry.Origin = new Vector2(_MouseShape.Geometry.Width / 2, _MouseShape.Geometry.Height / 2);
            // make the collision detection of the MouseShape active
            _Detector.SetupGeometry(_MouseShape.Geometry, true, false);

        }

        
        protected override void DeActivate()
        {
            // release all references on deactivation
            _MouseShape = null;
            while (_Shapes.Count > 0)
            {
                _Shapes[0].Geometry.Dispose();
                _Shapes.RemoveAt(0);
            }

            _Detector.Delete();

            // make sure that this sample will be initialized when the user returns to this sample
            ReInitializeOnNextActivation();
        }
        #endregion

        #region Update
        
        public override void Update(float seconds,
                                GamePadState currentButtons, GamePadState previousButtons,
                                KeyboardState currentKeys, KeyboardState previousKeys,
                                MouseState currentMouse, MouseState previousMouse)
        {
            // ...
            // set mouse shape to cursor's position
            _Cursor.Update(seconds, currentButtons, previousButtons, currentKeys, previousKeys, currentMouse, previousMouse);
            _MouseShape.Geometry.Position = _Cursor.Position;

            // ...
            // Check collision
            _MouseShape.Color = NormalColor; // set to normal color before detector checks again for collision color...
            _Detector.CheckCollision();

           
            // ...
            // change accuracy of collision detection
            if (currentKeys.IsKeyDown(Keys.OemPlus) || currentButtons.IsButtonDown(Buttons.RightTrigger))
            {
                _Detector.Accuracy = Math.Min(_Detector.Accuracy + seconds, 20);
                SetText();
            }
            if (currentKeys.IsKeyDown(Keys.OemMinus) || currentButtons.IsButtonDown(Buttons.LeftTrigger))
            {
                _Detector.Accuracy = Math.Max(_Detector.Accuracy - seconds, 0.1f);
                SetText();
            }

            // transform current shape
            // rotate
            if (currentKeys.IsKeyDown(Keys.Left) || currentButtons.IsButtonDown(Buttons.DPadLeft))
                _MouseShape.Geometry.Rotation += seconds;
            if (currentKeys.IsKeyDown(Keys.Right) || currentButtons.IsButtonDown(Buttons.DPadRight))
                _MouseShape.Geometry.Rotation -= seconds;

            // scale
            if (currentKeys.IsKeyDown(Keys.Up) || currentButtons.IsButtonDown(Buttons.DPadUp))
            {
                _MouseShape.Geometry.Scale = new Vector2(_MouseShape.Geometry.Scale.X + seconds, _MouseShape.Geometry.Scale.Y + seconds);
            }
            if (currentKeys.IsKeyDown(Keys.Down) || currentButtons.IsButtonDown(Buttons.DPadDown))
            {
                _MouseShape.Geometry.Scale = new Vector2(_MouseShape.Geometry.Scale.X - seconds, _MouseShape.Geometry.Scale.Y - seconds);
            }
        }

   
        #endregion

        #region Draw
        public override void Draw(SpriteBatch batch)
        {
            Game.GraphicsDevice.Clear(Color.Teal);

            batch.Begin();

            // draw every shape's Texture
            for(int i = 0; i < _Shapes.Count; i++)
            {
                _Shapes[i].Draw(batch); // the color and position and all this is saved within the GeometrySprite
            }

            batch.End();
        }
        #endregion

        #region other methods

        // Method is called from CursorHelper when a Button has been released
        void _Cursor_ButtonReleased(TestGame.HelperClasses.CursorButton button)
        {
            // reset Color of old MouseShape
            _MouseShape.Color = NormalColor;
            // make previous mouseShape passive
            _Detector.SetupGeometry(_MouseShape.Geometry, false, true);

            // find current shape
            int i;
            for (i = 0; i < _Shapes.Count; i++)
            {
                if (_MouseShape == _Shapes[i]) // ah... there it is.
                {
                    // before we leave the loop, i will set to the shape after the current mouse-Shape
                    i = (i + 1) % _Shapes.Count;
                    break;
                }
            }

            _MouseShape.Geometry.Position = _Shapes[i].Geometry.Position;   // swap positions
            _MouseShape = _Shapes[i];                                       // link next shape to the mouse
            _Detector.SetupGeometry(_MouseShape.Geometry, true, false); // make new mouseShape active

        }

        // this method is called, when the CollisionDetector detected a collision
        void Detector_OnCollision(object sender, CollisionEventArgs args)
        {
            _MouseShape.Color = CollisionColor;
        }

        // due to displaying the current accuracy of the collision detection
        // it is required to reset the description...
        void SetText()
        {
            Descripton = "Collision Sample 1:\n"
            + "Move the cursor (with mouse, WASD or the left thumb stick) to hit other Shapes.\n"
            + "click / press SPACE / Press A-Button to change the shape.\n"
            + "Use the arrow keys / the D-Pad to rotate and scale the current shape.\n\n"
            + "To change the detection accuracy, press + / RT or - / LT (accuracy of 1 is a per-pixel detection)\n"
            + "Current accuracy: " + _Detector.Accuracy.ToString();
        }

        // create a texture fill with the "deer.jpg" with a random transformation of the picture
        Random rand = new Random();
        FillTexture GetRandomFill()
        {
            FillTexture fill = new FillTexture("deer");
            fill.Transform.Position = new Vector2(rand.Next((int)fill.Transform.Width), rand.Next((int)fill.Transform.Height));
            fill.Transform.Scale = new Vector2(3 * (float)rand.NextDouble(), 3 * (float)rand.NextDouble());
            fill.Transform.Rotation = MathHelper.TwoPi * (float)rand.NextDouble();
            return fill;
        }
        #endregion
    }
}
